library(igraph)
## 
## Attaching package: 'igraph'
## The following objects are masked from 'package:stats':
## 
##     decompose, spectrum
## The following object is masked from 'package:base':
## 
##     union
library(igraphdata)
library(visNetwork)
data(package = "igraphdata")
data(foodwebs)

Why igraph?

What are networks?

A “graph” in mathematics is simply a set of vertices paired with the corresponding set of edges. A “network” is that, in addition to the other variables and properties associated with the vertices and edges

Examples:

Making a network from scratch

g<-make_empty_graph( directed = FALSE)
g <- g + vertices('Bio', 'Eco', 'CS', 'Soc', 'Psych', 'Econ')
plot(g, vertex.label.dist=3) #Add a little label distance so the labels dont overlap with the nodes. Easier to see

Adding edges is simple

g <- g + edges(c('CS','Bio', 'CS','Eco', 'CS','Econ', 'CS','Soc', 'CS','Psych')) 
plot(g, vertex.label.dist=3)

You dont have to add all your edges at once

g <- g + edges(c('Bio','Eco', 'Eco','Econ', 'Soc','Psych', 'Soc','Econ'))
plot(g, vertex.label.dist=3)

Data structures of Networks

Edge list

If you do not have any isolated nodes sitting unconnected, then just entering the list of connections is enough, without explicitly stating the nodes

edgeList <- matrix( c('CS','Bio', 'CS','Eco', 'CS','Econ', 'CS','Soc', 'CS','Psych', 'Bio','Eco', 'Eco','Econ', 'Soc','Psych', 'Soc','Econ' ), nc = 2, byrow = TRUE)
print(edgeList)
##       [,1]  [,2]   
##  [1,] "CS"  "Bio"  
##  [2,] "CS"  "Eco"  
##  [3,] "CS"  "Econ" 
##  [4,] "CS"  "Soc"  
##  [5,] "CS"  "Psych"
##  [6,] "Bio" "Eco"  
##  [7,] "Eco" "Econ" 
##  [8,] "Soc" "Psych"
##  [9,] "Soc" "Econ"
edgeList2 <- as_edgelist(g, names = TRUE)
print(edgeList2)
##       [,1]  [,2]   
##  [1,] "Bio" "CS"   
##  [2,] "Eco" "CS"   
##  [3,] "CS"  "Econ" 
##  [4,] "CS"  "Soc"  
##  [5,] "CS"  "Psych"
##  [6,] "Bio" "Eco"  
##  [7,] "Eco" "Econ" 
##  [8,] "Soc" "Psych"
##  [9,] "Soc" "Econ"
gEdgeList<-graph_from_edgelist(edgeList, directed=FALSE)
plot(gEdgeList, vertex.label.dist=3)

gEdgeList2<-graph_from_edgelist(edgeList2, directed=FALSE)
plot(gEdgeList2, vertex.label.dist=3)

You can imagine how this would be useful if your data came in the form of a list of edges, as mine has in the past.

Adjacency Matrix

adjMatG <- as_adjacency_matrix(g) #get the adjacency matrix of g
print(adjMatG)
## 6 x 6 sparse Matrix of class "dgCMatrix"
##       Bio Eco CS Soc Psych Econ
## Bio     .   1  1   .     .    .
## Eco     1   .  1   .     .    1
## CS      1   1  .   1     1    1
## Soc     .   .  1   .     1    1
## Psych   .   .  1   1     .    .
## Econ    .   1  1   1     .    .
gAdjMat <- graph_from_adjacency_matrix(adjMatG, mode = "undirected") #create a new network from the adjacency matrix
plot(gAdjMat, vertex.label.dist=3)

In general, there are a million ways you can make the same network, and some are easier than others given the data you have and the format it is in. If one way is very hard, consider looking into another way.

Directed Networks

Directed networks are networks where an a connection between two nodes points from one node to the other in a way where the direciton of the pointing is important. Often the direction implies that something flows in that direction

Examples:

Simple example of directed network:

adjMat <-matrix(data = 0, nrow=7,ncol=7)
species <- c('coyote', 'vulture', 'snake', 'grass', 'bug', 'hawk', 'mouse')
rownames(adjMat) <- colnames(adjMat) <- species
print(adjMat)
##         coyote vulture snake grass bug hawk mouse
## coyote       0       0     0     0   0    0     0
## vulture      0       0     0     0   0    0     0
## snake        0       0     0     0   0    0     0
## grass        0       0     0     0   0    0     0
## bug          0       0     0     0   0    0     0
## hawk         0       0     0     0   0    0     0
## mouse        0       0     0     0   0    0     0
adjMat["grass","bug"] <-1
adjMat["bug","hawk"] <-1


adjMat["grass","mouse"] <-1
adjMat["mouse","hawk"] <-1
adjMat["mouse","coyote"] <-1
adjMat["mouse","snake"] <-1
adjMat["mouse","vulture"] <-1

adjMat["hawk","coyote"] <-1
adjMat["coyote","vulture"] <-1

adjMat["snake","vulture"] <-1




print(adjMat)
##         coyote vulture snake grass bug hawk mouse
## coyote       0       1     0     0   0    0     0
## vulture      0       0     0     0   0    0     0
## snake        0       1     0     0   0    0     0
## grass        0       0     0     0   1    0     1
## bug          0       0     0     0   0    1     0
## hawk         1       0     0     0   0    0     0
## mouse        1       1     1     0   0    1     0
foodWeb <- graph_from_adjacency_matrix(adjMat)
plot(foodWeb, vertex.label.dist=3)

is_weighted(foodWeb)
## [1] FALSE

Weighted Networks

Weighted networks are networks where edges come with “weight” which usually implies something about the “strength” or “length” of that edge.

Examples:

#Check if your network is weighted
is_weighted(foodWeb)
## [1] FALSE
adjMat["grass","bug"] <-0.2
adjMat["bug","hawk"] <-1


adjMat["grass","mouse"] <-0.8
adjMat["mouse","hawk"] <-0.2
adjMat["mouse","coyote"] <-0.2
adjMat["mouse","snake"] <-0.2
adjMat["mouse","vulture"] <-0.4

adjMat["hawk","coyote"] <-1
adjMat["coyote","vulture"] <-1

adjMat["snake","vulture"] <-1




print(adjMat)
##         coyote vulture snake grass bug hawk mouse
## coyote     0.0     1.0   0.0     0 0.0  0.0   0.0
## vulture    0.0     0.0   0.0     0 0.0  0.0   0.0
## snake      0.0     1.0   0.0     0 0.0  0.0   0.0
## grass      0.0     0.0   0.0     0 0.2  0.0   0.8
## bug        0.0     0.0   0.0     0 0.0  1.0   0.0
## hawk       1.0     0.0   0.0     0 0.0  0.0   0.0
## mouse      0.2     0.4   0.2     0 0.0  0.2   0.0
foodWeb <- graph_from_adjacency_matrix(adjMat, weighted=TRUE)
plot(foodWeb, vertex.label.dist=3)

#Notice that nothing looks different

#Now check if your network is weighted again
is_weighted(foodWeb)
## [1] TRUE
#Label the edges with weights
plot(foodWeb, vertex.label.dist=3, edge.label=E(foodWeb)$weight)

#Labelling edges is good when you need to know the exact values of weights, but it gets messy with big or dense networks(with alot of edges)

#Map edge weights to the widths of plotted edges
E(foodWeb)$width <- E(foodWeb)$weight 
plot(foodWeb, vertex.label.dist=3)

#This is good except some edges are too thin


# Modify edge widths to make it look better
E(foodWeb)$width <- E(foodWeb)$weight*4 
plot(foodWeb, vertex.label.dist=3)

In my example I weighted edges normalized to 1, to signify what percent of a population was being eaten by what, edges can be weighted in many different ways and need not be normalized to one. The method of weighting edges depends on the context of the system.

Working with existing data

fwc <- foodwebs$CrystalC

plot(fwc, layout=layout_as_tree, vertex.label.dist=1.5)
## Warning in v(graph): At structural_properties.c:3346 :graph contains a
## cycle, partial result is returned

plot(fwc, layout=layout_as_tree, vertex.label = 1:length(V(fwc)))
## Warning in v(graph): At structural_properties.c:3346 :graph contains a
## cycle, partial result is returned

visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned

Vertex Attributes

Weight and direction are attributes of edges

Vertices also can have attributes.

So far we have used the vertex attribute “name”, but vertices can have all sorts of attributes

#Check current vertex attributes
vertex_attr_names(fwc)
## [1] "name"    "ECO"     "Biomass"

Continous vertex attributes

V(fwc)$size <- V(fwc)$Biomass #note that we are setting a new vertex attribute
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
V(fwc)$size <- log1p(V(fwc)$Biomass)
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned

Categorical vertex attributes

V(fwc)$color <- V(fwc)$ECO
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned

Network Analysis

Network analysis refers to analyzing existing networks

Centrality

#Degree
V(fwc)$size <- degree(fwc)
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
V(fwc)$size <- degree(fwc, mode="in")
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
V(fwc)$size <- degree(fwc, mode="out")
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
#Betweenness Centrality
V(fwc)$size <- log1p(betweenness(fwc))*5
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned
#Closeness
#Calculated as the reciprocal of the sum of the length of the shortest paths between the node and all other nodes in the graph. Thus, the more central a node is, the closer it is to all other nodes.
V(fwc)$size <- closeness(fwc)*10^4
## Warning in closeness(fwc): At centrality.c:2617 :closeness centrality is
## not well-defined for disconnected graphs
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned

Manipulating networks

fwc <- foodwebs$CrystalC
fwc1 <- delete_vertices(fwc, c('Input','Output', "Respiration", 'detritus' ))
#V(fwc1)$size <-1
visIgraph(fwc, layout="layout_as_tree")
## Warning in ctrl$objs[[1]](graph = ig, ...): At structural_properties.c:
## 3346 :graph contains a cycle, partial result is returned

Resources I used